﻿////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  © 2012 Stanislav S. Yarmonov.
//
//  TODO:LICENSE
//     
////////////////////////////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;

namespace Pandora.Development.Parser
{
    public static class ParseExtensions
    {
        public static Parse<TToken, TResult> Not<TToken, TResult>(this Parse<TToken, TResult> parse, Parse<TToken, TResult> exclusion)
        {
            return input =>
            {
                return exclusion(input).If<TToken, TResult>(
                    success => input.CreateFailure<TResult>(ParseError.Format("Unexpected input", input.Current)),
                    failure => parse(input)
                );
            };
        }

        public static Parse<TToken, TResult> And<TToken, TInput, TResult>(this Parse<TToken, TInput> parse, Func<TInput, Parse<TToken, TResult>> and)
        {
            return input =>
            {
                return parse(input).If<TToken, TInput, TResult>(
                    success => and(success.Result)(success.Remains),
                    failure => input.CreateFailure<TResult>(failure.Error)
                );
            };
        }

        public static Parse<TToken, TResult> Or<TToken, TResult>(this Parse<TToken, TResult> parse, Parse<TToken, TResult> or)
        {
            return input =>
            {
                return parse(input).If<TToken, TResult>(
                    success => success,
                    failure => or(input)
                );
            };
        }

        public static Parse<TToken, TResult> Xor<TToken, TResult>(this Parse<TToken, TResult> parse, Parse<TToken, TResult> xor)
        {
            return input =>
            {
                return parse(input).If<TToken, TResult>(
                    success => xor(input).If<TToken, TResult>(
                        s => input.CreateFailure<TResult>(ParseError.String("XOR failed")),
                        f => success
                    ),
                    failure => xor(input).If<TToken, TResult>(
                        s => s,
                        f => input.CreateFailure<TResult>(ParseError.String("XOR failed"))
                    )
                );
            };
        }

        public static Parse<TToken, IEnumerable<TResult>> Many<TToken, TResult>(this Parse<TToken, TResult> parse, int min = 0, int max = int.MaxValue)
        {
            //TODO:exception when min>max, min <0.

            return input =>
            {
                int count = 0;
                List<TResult> list = new List<TResult>();
                IParseResults<TToken, TResult> results = null;
                IParseInput<TToken> remains = input;

                while ((results = parse(remains)).IsSuccess() && count < max)
                {
                    IParseSuccess<TToken, TResult> success = results.AsSuccess();
                    list.Add(success.Result);
                    remains = success.Remains;
                    count++;
                }

                if (count < min)
                {
                    return input.CreateFailure<IEnumerable<TResult>>(ParseError.Format("{0} items expected, {1} processed", min, count));
                }
                else
                {
                    return remains.CreateSuccess<IEnumerable<TResult>>(list);
                }
            };
        }

        public static Parse<TToken, TResult> Failure<TToken, TResult>(this Parse<TToken, TResult> parse, IParseError error)
        {
            return input => input.CreateFailure<TResult>(error);
        }

        public static Parse<TToken, TResult> Select<TToken, TInput, TResult>(
            this Parse<TToken, TInput> parse,
            Func<TInput, TResult> mapping
        )
        {
            return parse.And<TToken, TInput, TResult>(value => input => input.CreateSuccess(mapping(value)));
        }

        public static Parse<TToken, TResult> SelectMany<TToken, TInput, TProjection, TResult>(
            this Parse<TToken, TInput> parse,
            Func<TInput, Parse<TToken, TProjection>> selector,
            Func<TInput, TProjection, TResult> projector
        )
        {
            return parse.And(input =>
                selector(input)
                .Select(projection => projector(input, projection))
            );
        }

        //TODO:
        public static Parse<TToken, TResult> Debug<TToken, TResult>(
            this Parse<TToken, TResult> parse,
            Func<IParseInput<TToken>, IParseResults<TToken, TResult>, string> generator)
        {
            return input =>
            {
                IParseResults<TToken, TResult> results = parse(input);
                Console.WriteLine(generator(input, results));
                return results;
            };
        }
    }
}
